package com.openway.game.personage.impl;

import com.openway.game.Game;
import com.openway.game.field.Coordinate;
import com.openway.game.field.Direction;
import com.openway.game.personage.AbstractMovingGameObject;
import com.openway.game.personage.GameObject;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * @author Created: 27.02.12 19:33
 * @version $Date$ $Revision$
 */
public class Drunkard extends AbstractMovingGameObject {
    //Where drunkard gets after going out from the pub
    private static final Coordinate INITIAL_COORDINATE = new Coordinate(0, 9);
    private static final double BOTTLE_LOSING_PROBABILITY = 1.0 / 30;

    private State state;
    private boolean bottleIsLost = false;

    public Drunkard() {
        state = State.IN_PUB; //Drunkard is created in Pub
    }

    @Override
    public boolean canMove() {
        return state == State.WALKING || state == State.IN_PUB;
    }

    @Override
    public void makeMove() {
        if (state != State.IN_PUB) {
            Direction direction = getMoveDirection();
            executeMove(direction);
        } else {
            goOutFromPub();
        }
    }

    private void goOutFromPub() {
        GameObject objectInTargetCoordinate = Game.getInstance().getPlayingField().getObject(INITIAL_COORDINATE);

        boolean canChangePosition = handleCollisions(objectInTargetCoordinate);
        if (canChangePosition) {
            Game.getInstance().getPlayingField().addObject(this, INITIAL_COORDINATE);
            state = State.WALKING;
            afterChangePosition(objectInTargetCoordinate);
        }
    }

    private boolean executeMove(Direction direction) {
        Coordinate drinkerCoordinate = Game.getInstance().getPlayingField().getObjectCoordinate(this);
        GameObject objectInTargetCoordinate = Game.getInstance().getPlayingField().getAdjacentObject(drinkerCoordinate, direction);

        //decide whether the bottle is lost at this step
        boolean loseBottle = false;
        if (!this.bottleIsLost && Math.random() < BOTTLE_LOSING_PROBABILITY) {
            loseBottle = true;
        }

        boolean canChangePosition = handleCollisions(objectInTargetCoordinate);
        if (canChangePosition) {
            Game.getInstance().getPlayingField().moveObject(this, direction);
            afterChangePosition(objectInTargetCoordinate);

            //lose bottle
            if (loseBottle) {
                Game.getInstance().getPlayingField().addObject(new Bottle(), drinkerCoordinate);
                this.bottleIsLost = true;
            }
        }

        return canChangePosition;
    }

    /**
     * Handling collision with other objects.
     * <p/>
     * This method is responsible for the compliance of game rule:
     * If the drinker runs into the post, he doesn't move and falls asleep standing.
     * If the drinker runs into the lamp, he doesn't move.
     * If the drinker runs into other walking drinker or policeman, then he doesn't move.
     * If the drinker runs into other sleep lying drinker, then he doesn't move.
     * If the drinker runs into other sleep standing drinker, he doesn't move and falls asleep standing.
     *
     * @return true if drunkard can change position, else otherwise.
     */
    private boolean handleCollisions(GameObject objectInTargetCoordinate) {
        if (objectInTargetCoordinate == null)
            return true;

        //check if runs into the post
        if (objectInTargetCoordinate instanceof Post) {
            if (state != State.IN_PUB) //drunkard can not sleep in pub
                this.state = State.SLEEP_STANDING;
            return false;
        }

        //check if runs into the lamp or policeman
        if (objectInTargetCoordinate instanceof Lamp || objectInTargetCoordinate instanceof Policeman) {
            return false;
        }

        //check if runs into other drunker
        if (objectInTargetCoordinate instanceof Drunkard) {
            Drunkard otherDrunkard = (Drunkard) objectInTargetCoordinate;
            switch (otherDrunkard.state) {
                case SLEEP_STANDING:
                    if (state != State.IN_PUB) //drunkard can not sleep in pub
                        this.state = State.SLEEP_STANDING;
                    return false;
                case SLEEP_LYING:
                case WALKING:
                    return false;
            }
        }

        return true;
    }

    /**
     * After moving processing.
     * If the drinker runs into a bottle, he falls asleep lying.
     */
    private void afterChangePosition(GameObject objectInTargetCoordinate) {
        //check if runs into bottle
        if (objectInTargetCoordinate instanceof Bottle) {
            this.state = State.SLEEP_LYING;
        }
    }

    private Direction getMoveDirection() {
        //choose random possible direction (drunkard should not move outside the field)
        List<Direction> directions = new ArrayList<Direction>();
        for (Direction direction : Direction.values()) {
            if (Game.getInstance().getPlayingField().isInsideDirection(this, direction))
                directions.add(direction);
        }

        Random random = new Random();
        int randomDirectionIndex = random.nextInt(directions.size());
        return directions.get(randomDirectionIndex);
    }


    //getters for different states of drunkard
    public boolean isSleepStanding() {
        return this.state == State.SLEEP_STANDING;
    }

    public boolean isSleepLying() {
        return this.state == State.SLEEP_LYING;
    }

    public boolean isSleeping() {
        return this.state == State.SLEEP_LYING || this.state == State.SLEEP_STANDING;
    }

    public boolean isWalking() {
        return this.state == State.WALKING;
    }

    @Override
    public String toString() {
        Coordinate coordinate = Game.getInstance().getPlayingField().getObjectCoordinate(this);
        return "Drunkard{" +
                "id=" + getId() +
                ", state=" + state +
                ", coordinate=" + coordinate +
                '}';
    }

    private static enum State {
        SLEEP_LYING, SLEEP_STANDING, WALKING, IN_PUB
    }
}
